﻿using Devil;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace GameToolkit.UI
{
    /// <summary>
    /// 必要窗口意图数据
    /// </summary>
    public interface IPanelIntentRequired { }

    /// <summary>
    /// 窗口意图处理
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IPanelIntentHandler<in T>
    {
        bool OnHandleIntent(T intent);
    }

    /// <summary>
    /// 终止意图参数
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IPanelIntentTerminate<in T>
    {
        void OnTerminateIntent(T intent);
    }

    public enum EPanelType
    {
        Normal,
        Dialog,
        Status,
    }

    /// <summary>
    /// 窗口事件回调
    /// </summary>
    public interface IPanelEventHandler { }

    public interface IOpenPanelHandler : IPanelEventHandler
    {
        IPanelProgressive OnPanelOpened();
        IPanelProgressive OnPanelClosed();
    }

    public interface IFocusPanelHandler : IPanelEventHandler
    {
        void OnBecomeForeground();
        void OnBecomeBackground();
    }

    public interface IPanelProgressive
    {
        bool IsComplete { get; }
        void StartProgress();
        void StopProgress();
    }

    [RequireComponent(typeof(Canvas)), DisallowMultipleComponent]
    public class Panel : MonoBehaviour
    {
        internal enum EProgressive : byte
        {
            None,
            Open,
            Close,
        }

        class Progressive
        {
            bool mIsProcess;
            IPanelProgressive mProgressive;
            internal Progressive(IPanelProgressive progressive)
            {
                mProgressive = progressive;
            }
            internal bool IsComplete => !mIsProcess || mProgressive.IsComplete;
            internal bool IsProcess
            {
                get { return mIsProcess; }
                set
                {
                    if (mIsProcess != value)
                    {
                        mIsProcess = value;
                        if (value)
                        {
                            mProgressive.StartProgress();
                        }
                        else
                        {
                            mProgressive.StopProgress();
                        }
                    }
                }
            }
        }

        Canvas mCanvas;

        [SerializeField]
        EPanelType m_PanelType;

        [SerializeField]
        int m_StatusOrder;

        [SerializeField]
        SelectableNavigation m_DefaultSelectableGroup;

        [SerializeField]
        GraphicRaycaster[] m_Raycasters;

        //[SerializeField, GameButton]
        //string m_ExitBtn;
        //public string EscName => m_ExitBtn;

        bool mIsInitialized;
        PanelManager.IUIButton mEscBtn;
        IOpenPanelHandler[] mOpenHandler;
        IFocusPanelHandler[] mFocusHandler;
        bool mHasFocus;
        bool mIsOpened;
        EProgressive mCurrentProgressive;
        RandomList<Progressive> mProgressives;
        protected virtual string EscBtnName => null;
        public PanelManager.IUIButton EscBtn => mEscBtn;
        internal EProgressive CurrentProgressive => mCurrentProgressive;
        public EPanelType PanelType => m_PanelType;
        public int StatusOrder => m_StatusOrder;
        public virtual SelectableNavigation SelectableGroup => m_DefaultSelectableGroup;
        public Camera UICamera
        {
            get
            {
                var can = GetCanvas();
                return can == null ? null : can.worldCamera;
            }
        }

        internal void Initialize(bool alwaysInit = false)
        {
            if (!mIsInitialized || alwaysInit)
            {
                mIsInitialized = true;
                mOpenHandler = GetComponents<IOpenPanelHandler>();
                mFocusHandler = GetComponents<IFocusPanelHandler>();
                mCanvas = GetComponent<Canvas>();
                if (mProgressives == null)
                    mProgressives = new RandomList<Progressive>(mOpenHandler.Length);

                var focus = IsFocused;
                SetRaycasterEnabled(focus);
            }
        }

        public bool IsFocused
        {
            get { return mHasFocus && mIsOpened && mCurrentProgressive == EProgressive.None; }
            internal set
            {
                if (mHasFocus != value)
                {
                    var focus = mHasFocus && mIsOpened && mCurrentProgressive == EProgressive.None;
                    mHasFocus = value;
                    var next = mHasFocus && mIsOpened && mCurrentProgressive == EProgressive.None;
                    if (focus && !next)
                        InvokeFocusOffEvent();
                    else if (!focus && next)
                        InvokeFocusOnEvent();
                }
            }
        }

        public bool IsOpened
        {
            get { return mIsOpened; }
            internal set
            {
                if(mIsOpened != value)
                {
                    var wasOpen = mIsOpened;
                    var focus = mHasFocus && mIsOpened && mCurrentProgressive == EProgressive.None;
                    mIsOpened = value;
                    var next = mHasFocus && mIsOpened && mCurrentProgressive == EProgressive.None;
                    // background
                    if (!next && focus)
                        InvokeFocusOffEvent();
                    // open/close
                    if (wasOpen && !mIsOpened)
                        InvokeCloseEvent();
                    else if (!wasOpen && mIsOpened)
                        InvokeOpenEvent();
                    // foreground
                    if (next && !focus)
                        InvokeFocusOnEvent();
                }
            }
        }

        internal void ValidateVisible()
        {
            OnSetVisible(IsOpened || mCurrentProgressive != EProgressive.None);
        }

        void SetRaycasterEnabled(bool enableRaycaster)
        {
            if (m_PanelType != EPanelType.Status)
            {
                var len = m_Raycasters == null ? 0 : m_Raycasters.Length;
                for (int i = 0; i < len; i++)
                {
                    var raycaster = m_Raycasters[i];
                    if (raycaster != null)
                        raycaster.enabled = enableRaycaster;
                }
            }
        }

        void InvokeFocusOnEvent()
        {
            var escName = EscBtnName;
            if (mEscBtn == null && !string.IsNullOrEmpty(escName))
            {
                mEscBtn = PanelManager.RegistKeyButton(this, escName);
                if (mEscBtn != null)
                    mEscBtn.AddCallback(CloseSelf);
            }
            SetRaycasterEnabled(true);
            var sel = EventSystem.current == null ? null : EventSystem.current.currentSelectedGameObject;
            if (sel != null && !sel.transform.IsChildOf(transform))
            {
                var nav = SelectableGroup;
                if (nav != null)
                    nav.SelectDefault();
                else
                    EventSystem.current.SetSelectedGameObject(null);
            }

            var len = mFocusHandler == null ? 0 : mFocusHandler.Length;
            for (int i = 0; i < len; i++)
            {
                mFocusHandler[i].OnBecomeForeground();
            }
        }

        void InvokeFocusOffEvent()
        {
            var btn = mEscBtn;
            mEscBtn = null;
            if (btn != null)
            {
                PanelManager.UnregistKeyButton(btn);
            }
            var len = mFocusHandler == null ? 0 : mFocusHandler.Length;
            for (int i = 0; i < len; i++)
            {
                mFocusHandler[i].OnBecomeBackground();
            }
            SetRaycasterEnabled(false);
        }

        void InvokeOpenEvent()
        {
            Initialize();
            var len = mOpenHandler == null ? 0 : mOpenHandler.Length;
            for (int i = mProgressives.Count - 1; i >= 0; i--)
            {
                mProgressives[i].IsProcess = false;
            }
            mProgressives.Clear();
            OnSetVisible(true);
            for (int i = 0; i < len; i++)
            {
                var progressive = mOpenHandler[i].OnPanelOpened();
                if (progressive != null)
                {
                    var proc = new Progressive(progressive);
                    proc.IsProcess = true;
                    mProgressives.Add(proc);
                }
            }
            mCurrentProgressive = mProgressives.Count > 0 ? EProgressive.Open : EProgressive.None;
            var mgr = PanelManager.Instance;
            if (mgr != null)
                mgr.NotifyOpen(this);
        }


        void InvokeCloseEvent()
        {
            var mgr = PanelManager.Instance;
            if (mgr != null)
                mgr.NotifyClose(this);

            var len = mOpenHandler == null ? 0 : mOpenHandler.Length;
            for (int i = mProgressives.Count - 1; i >= 0; i--)
            {
                mProgressives[i].IsProcess = false;
            }
            mProgressives.Clear();
            for (int i = 0; i < len; i++)
            {
                var progressive = mOpenHandler[i].OnPanelClosed();
                if (progressive != null)
                {
                    var proc = new Progressive(progressive);
                    proc.IsProcess = true;
                    mProgressives.Add(proc);
                }
            }
            mCurrentProgressive = mProgressives.Count > 0 ? EProgressive.Close : EProgressive.None;
            if (mCurrentProgressive == EProgressive.None)
                OnSetVisible(false);
        }

        internal bool ExecuteProgressive()
        {
            if (mCurrentProgressive != EProgressive.None)
            {
                for (int i = mProgressives.Count - 1; i >= 0; i--)
                {
                    var proc = mProgressives[i];
                    if (proc.IsComplete)
                    {
                        proc.IsProcess = false;
                        mProgressives.RemoveAt(i);
                    }
                }
                if (mProgressives.Count == 0)
                {
                    var focus = mHasFocus && mIsOpened && mCurrentProgressive == EProgressive.None;
                    mCurrentProgressive = EProgressive.None;
                    var next = mHasFocus && mIsOpened && mCurrentProgressive == EProgressive.None;
                    if (focus && !next)
                        InvokeFocusOffEvent();
                    else if (!focus && next)
                        InvokeFocusOnEvent();
                    if (!mIsOpened)
                        OnSetVisible(false);
                }
            }
            return mCurrentProgressive != EProgressive.None;
        }

        public Canvas GetCanvas()
        {
            Initialize(false);
            return mCanvas;
        }

#if UNITY_EDITOR
        [ContextMenu("Close This Panel")]
#endif
        public virtual void CloseSelf()
        {
            PanelManager.Instance?.ClosePanel(this);
        }

        protected virtual void OnEnable() { }

        protected virtual void OnDisable()
        {
            if (mProgressives != null)
            {
                for (int i = mProgressives.Count - 1; i >= 0; i--)
                {
                    mProgressives[i].IsProcess = false;
                }
                mProgressives.Clear();
            }
        }

        protected virtual void OnSetVisible(bool visible)
        {
            gameObject.SetActive(visible);
        }

        /// <summary>
        /// 等同于 Update， 但该方法仅在窗口至于顶层时由UI系统调用
        /// </summary>
        public virtual void OnForegroundUpdate()
        {
        }

#if UNITY_EDITOR

        protected virtual void OnValidate()
        {
            Initialize(true);
            if (!Application.isPlaying)
            {
                if (m_PanelType == EPanelType.Status)
                {
                    var canvas = GetCanvas();
                    if (canvas != null && canvas.sortingOrder != m_StatusOrder)
                    {
                        canvas.sortingOrder = m_StatusOrder;
                        EditorUtility.SetDirty(canvas);
                    }
                }
                else
                {
                    var raycasters = m_Raycasters;
                    m_Raycasters = GetComponentsInChildren<GraphicRaycaster>();
                    var len1 = raycasters == null ? 0 : raycasters.Length;
                    var len2 = m_Raycasters == null ? 0 : m_Raycasters.Length;
                    var dirty = len1 != len2;
                    for (int i = 0; !dirty && i < len1; i++)
                    {
                        dirty = raycasters[i] != m_Raycasters[i];
                    }
                    if (dirty)
                        UnityEditor.EditorUtility.SetDirty(this);
                }
            }
        }

        [ContextMenu("Open This Panel", false)]
        bool CanOpen()
        {
            if (PanelManager.Instance == null || !AssetDatabase.Contains(this))
                return false;
            return true;
        }

        [ContextMenu("Open This Panel")]
        void OpenThis()
        {
            var path = AssetDatabase.GetAssetPath(this);
            if (!string.IsNullOrEmpty(path) && PanelManager.Instance != null)
                PanelManager.Instance.OpenPanel(path);

        }

#endif

        public class FadeProgressive : IPanelProgressive, IYieldCommand
        {
            Panel mPanel;
            float mProgress;
            float mSpeed;
            float mAlpha;
            bool mInit;
            float mInitAlpha = 1f;
            ParallelDispatcher.Handler mJob;
            public FadeProgressive(Panel panel, float alpha, float duration = 0.5f)
            {
                mPanel = panel;
                mAlpha = alpha;
                mSpeed = duration > 0 ? (1f / duration) : 1f;
            }

            bool IPanelProgressive.IsComplete => !mJob.IsAlive;

            public void SetAlphaStartValue(float value)
            {
                mInit = true;
                mInitAlpha = value;
            }
            public void SetAlphaFinalValue(float value)
            {
                mAlpha = value;
            }

            IEnumerator IYieldCommand.Execute()
            {
                if (mPanel == null)
                    yield break;
                mProgress = 0f;
                var grp = mPanel.GetComponentsInChildren<CanvasGroup>();
                if (grp == null || grp.Length == 0)
                {
                    grp = new CanvasGroup[1];
                    grp[0] = mPanel.gameObject.AddComponent<CanvasGroup>();
                }
                var alphas = new float[grp.Length];
                for (int i = 0; i < alphas.Length; i++)
                {
                    alphas[i] = mInit || grp[i] == null ? mInitAlpha : grp[i].alpha;
                }
                while(mProgress < 1f)
                {
                    mProgress += Time.unscaledDeltaTime * mSpeed;
                    var v = Mathf.Clamp01(mProgress);
                    v = v * v * v;
                    for (int i = 0; i < alphas.Length; i++)
                    {
                        var item = grp[i];
                        if (item != null)
                        {
                            item.alpha = Mathf.Lerp(alphas[i], mAlpha, v);
                        }
                    }
                    yield return null;
                }
            }

            void IPanelProgressive.StartProgress()
            {
                mJob.Abort();
                mJob = this.Schedule(true);
            }

            void IPanelProgressive.StopProgress()
            {
                mJob.Abort();
            }
        }
    }
}
